# -*- coding:utf-8 -*-
__author__ = 'zuozhu'

import os
#import tools.config as config
import config
import shutil
import subprocess
import time
import csv
import glob

# 1. 更换工作目录
print('workDir: %s'%os.getcwd())
os.chdir('../')
print('workDir: %s'%os.getcwd())

from enum import Enum
class CompareCode(Enum):
    SUCCESS = 0       # 比较成功
    NO_OUT_FILE = -1  # 没有输出文件
    OUT_ERR = -2      # 答案错误

class ExeRunCode(Enum):
    NONE = 0     # 无效值
    SUCCESS = 1  # 运行成功
    TIMEOUT = -1 # 运行错误
    RUN_ERR = -2 # 运行超时

# 检查测试数据和测试答案是否齐全
def checkTestData():
    success = True
    for problem in config.problemList:
        for i in range(1, problem['test_cnt'] + 1):
            testFileDir = os.path.join(config.dataDir, problem['english_name'])
            testInFilePath = os.path.join(testFileDir, ''.join([problem['english_name'], str(i), '.in']))
            if not os.path.exists(testInFilePath):
                print('Error:=== 缺少测试输入文件[%d], problem: %s, total: %d==='%(i, problem['english_name'], problem['test_cnt']))
                success = False
                break
            testAnsFilePath = os.path.join(testFileDir, ''.join([problem['english_name'], str(i), '.ans']))
            if not os.path.exists(testAnsFilePath):
                print('Error:=== 缺少测试答案文件[%d] problem: %s, total: %d==='%(i, problem['english_name'], problem['test_cnt']))
                success = False
                break
        if not success:
            break
    return success

# 拷贝学生代码到评测文件夹，返回评测文件夹路径
def copyStuCodeToJudgeDir():
    codeJudgeDir = ''.join([config.codeDir, '.judge'])
    print('正在拷贝考生代码,评测目录: %s....'%(codeJudgeDir))
    # 移除已存在文件夹
    if os.path.exists(codeJudgeDir):
        shutil.rmtree(codeJudgeDir, ignore_errors=True)
    # 拷贝学生代码以备评测
    shutil.copytree(config.codeDir, codeJudgeDir)
    return codeJudgeDir

# 枚举考生人数和考生文件夹
def listStuName(codeJudgeDir):
    dirCnt=0
    students = []
    dirNames = os.listdir(codeJudgeDir)
    for dirName in dirNames:
        # 忽略以'-'开头的文件夹
        if dirName[0] == '-':
            continue
        dirCnt += 1
        print('考生[%d]: %s' % (dirCnt, dirName))
        students.append(dirName)
    return students

# 拷贝测试文件到评测目录
def copyTestInFilesToJugeDir(stuName, problem, codeJudgeDir):
    for i in range(1, problem['test_cnt'] + 1):
        testInFileName = ''.join([problem['english_name'], str(i), '.in'])
        testInFilePath = os.path.join(config.dataDir, problem['english_name'], testInFileName)
        testInDstPath =  os.path.join(codeJudgeDir, stuName, problem['english_name'], testInFileName)
        shutil.copy(testInFilePath, testInDstPath)

# 复制测试输入文件，去除测试文件的编号后缀
def cloneTestInFile(codeJudgeDir, stuName, i, problem):
    testInFileDir = os.path.join(codeJudgeDir, stuName, problem['english_name'])
    oldTestInFilePath = os.path.join(testInFileDir, ''.join([problem['english_name'], str(i), '.in']))
    # 去除测试编号以便满足测试要求
    newTestInFilePath = os.path.join(testInFileDir, ''.join([problem['english_name'],'.in']))
    shutil.copy(oldTestInFilePath, newTestInFilePath)

def compileStuCode(codeJudgeDir, stuName, problem):
    complieTool = 'g++'
    if config.codeLanguage == '.c':
        complieTool = 'gcc'
    print('Info:===========编译工具: %s==========='%(complieTool))

    stuCodeFile = ''.join([problem['english_name'], config.codeLanguage])
    stuCodeFilePath = os.path.join(codeJudgeDir, stuName, problem['english_name'], stuCodeFile)
    stuCodeExePath = os.path.join(codeJudgeDir, stuName, problem['english_name'], problem['english_name'])
    complieCmd=''.join([complieTool, ' -o ', stuCodeExePath, '  ', stuCodeFilePath])
    retCode = os.system(complieCmd)
    if retCode != 0:
        print('Error: ================编译错误================================')
    return retCode == 0

def isStuUpLoadCode(judgeDir, stuName, problem,):
    stuCodeFile = ''.join([problem['english_name'], config.codeLanguage])
    stuCodeFilePath = os.path.join(judgeDir, stuName, problem['english_name'], stuCodeFile)
    if not os.path.exists(stuCodeFilePath):
        print('Error: ================未找到代码================================')
        return False
    return  True

def generateEmptyScore(problem, msg):
    scoreDetail={}
    scoreDetail['total'] = 0
    scoreDetail['problem_name'] = problem['name']
    scoreDetail['english_name'] = problem['english_name']
    scoreDetail['test_detail'] = {}
    for i in range(1, problem['test_cnt']+1):
        testFileName = ''.join([problem['english_name'], str(i), '.in'])
        scoreDetail['test_detail'][testFileName] = msg
    return scoreDetail

def runStuExeWithTestIn(codeJudgeDir, stuName, i, problem):
    stuExeDir = os.path.join(os.getcwd(), codeJudgeDir, stuName, problem['english_name'])
    stuExeFilePath = os.path.join(stuExeDir, problem['english_name'])
    child = subprocess.Popen( stuExeFilePath, cwd=stuExeDir)
    statTimeMs = int(round(time.time() * 1000))
    endTimeMs = statTimeMs
    runTime = 0
    procStatus = child.poll()
    while (procStatus == None or procStatus == 1) and runTime < problem['time_limit'] * 1000 + 200:
        time.sleep(0.5)
        endTimeMs = int(round(time.time() * 1000))
        runTime = endTimeMs - statTimeMs
        procStatus = child.poll()
    try:
        print('Info: ##########retStatus: %d############'%(procStatus))
    except:
        print('Info: ##########Time Out Of Limit###########')
    retCode = ExeRunCode.NONE
    if procStatus == 0:
        retCode = ExeRunCode.SUCCESS
    elif procStatus == None or procStatus == 1:
        try:
            child.kill()      #终止子进程
            child.terminate() #终止子进程
        except:
            pass
        retCode = ExeRunCode.TIMEOUT
    else:
       retCode = ExeRunCode.RUN_ERR
    return retCode

def compareOutFile(judgeDir, stuName, i, problem):
    stuExeDir = os.path.join(os.getcwd(), judgeDir, stuName, problem['english_name'])
    stuExeOutPath = os.path.join(stuExeDir, ''.join([problem['english_name'], ".out"]))
    if not os.path.exists(stuExeOutPath):
        return CompareCode.NO_OUT_FILE

    correctOutDir = os.path.join(os.getcwd(), config.dataDir, problem['english_name'])
    correctOutPath = os.path.join(correctOutDir,  ''.join([problem['english_name'], str(i), '.ans']))
    stuOutData = ""
    try:
        stuOutData = open(stuExeOutPath).read()
    except:
        None
    correctOutData = open(correctOutPath).read()
    # 忽略掉末尾的换行和空格
    if stuOutData.rstrip() == correctOutData.rstrip():
        return CompareCode.SUCCESS
    return CompareCode.OUT_ERR

# 保存错误信息
def backupErrInfo(judgeDir, stuName, i, problem, errInfo):
    stuExeDir = os.path.join(os.getcwd(), judgeDir, stuName, problem['english_name'])
    # 创建错误备份文件夹
    errBackUpDir = os.path.join(stuExeDir, 'error_bak')
    if not os.path.exists(errBackUpDir):
        os.mkdir(errBackUpDir)

    # 1. 备份输入文件,并还原测试编号test.in --> testx_err.in
    testInFilePath = os.path.join(stuExeDir, ''.join([problem['english_name'],'.in']))
    bakupTestInFilePath = os.path.join(errBackUpDir, ''.join([problem['english_name'], str(i),'-', errInfo, '.in']))
    shutil.copy(testInFilePath, bakupTestInFilePath)

    # 备份测试输出
    stuExeOutPath = os.path.join(stuExeDir, ''.join([problem['english_name'], ".out"]))
    bakupTestOutFilePath = os.path.join(errBackUpDir, ''.join([problem['english_name'], str(i),'-', errInfo, '.out']))
    if os.path.exists(stuExeOutPath):
        shutil.copy(stuExeOutPath, bakupTestOutFilePath)

    # 备份正确输出
    correctOutDir = os.path.join(os.getcwd(), config.dataDir, problem['english_name'])
    correctOutPath = os.path.join(correctOutDir,  ''.join([problem['english_name'], str(i), '.ans']))
    bakupCorrectOutPath = os.path.join(errBackUpDir, ''.join([problem['english_name'], str(i), '.ans']))
    shutil.copy(correctOutPath, bakupCorrectOutPath)

def files(curr_dir = '.', ext = '*.exe'):
    for i in glob.glob(os.path.join(curr_dir, ext)):
        yield i

def removeFiles(rootdir, ext, show = False):
    for i in files(rootdir, ext):
        if show:
            print (i)
        os.remove(i)

# 评测考生题目得分
def judgeStuCode(judgeDir, stuName, problem):
    print("\n评测%s,题目[%s]：%s"%(stuName, problem['english_name'], problem['name']))

    # 判断考生是否有提交代码
    if not isStuUpLoadCode(judgeDir, stuName, problem):
        return generateEmptyScore(problem, '未提交代码')

    # 编译考生代码
    if not compileStuCode(judgeDir, stuName, problem):
        return generateEmptyScore(problem, '编译错误')

    # 拷贝所有测试输入文件到评测文件夹
    copyTestInFilesToJugeDir(stuName, problem, judgeDir)

    # 每个测试点的分数
    oneTestScore = problem['total_score'] / problem['test_cnt']
    scoreDetail={
        'total':0,  'problem_name': problem['name'],  'english_name': problem['english_name'],
        'test_detail':{}
    }

    # 更新测试输入评测考生代码
    for i in range(1, problem['test_cnt'] + 1):
        # 拷贝测试输入文件testx.in ---> test.in
        cloneTestInFile(judgeDir, stuName, i, problem)
        retCode = runStuExeWithTestIn(judgeDir, stuName, i, problem)
        testFileName = ''.join([problem['english_name'], str(i)])

        # 运行成功,则比较输出文件
        if retCode == ExeRunCode.SUCCESS:
            compareRet = compareOutFile(judgeDir, stuName, i, problem)
            if  compareRet == CompareCode.SUCCESS:
                print('Info: ========答案正确[%s%d]====================='%(problem['english_name'], i))
                scoreDetail['test_detail'][testFileName] = oneTestScore
                scoreDetail['total'] += oneTestScore
            elif compareRet == CompareCode.NO_OUT_FILE:
                print('Error: ========无输出文件[%s%d]====================='%(problem['english_name'], i))
                scoreDetail['test_detail'][testFileName] = '无输出文件'
                # 保存错误现场
                backupErrInfo(judgeDir, stuName, i, problem, 'NOUT')
            else:
                print('Error: ========答案错误[%s%d]====================='%(problem['english_name'], i))
                scoreDetail['test_detail'][testFileName] = '答案错误'
                # 保存错误现场
                backupErrInfo(judgeDir, stuName, i, problem, 'WA')
        elif retCode == ExeRunCode.TIMEOUT:
            print('Error: ========运行超时[%s%d]====================='%(problem['english_name'], i))
            scoreDetail['test_detail'][testFileName] = '运行超时'
            backupErrInfo(judgeDir, stuName, i, problem, 'TLE')
        else:
            print('Error: ========运行错误[%s%d]====================='%(problem['english_name'], i))
            scoreDetail['test_detail'][testFileName] = '运行错误'
            backupErrInfo(judgeDir, stuName, i, problem, 'RE')
    # 清除无用文件
    stuJudgeDir = os.path.join(os.getcwd(), judgeDir, stuName, problem['english_name'])
    removeFiles(stuJudgeDir, "*.in")
    removeFiles(stuJudgeDir, "*.out")
    removeFiles(stuJudgeDir, problem['english_name'])
    return scoreDetail

def saveScoreDetailToCsv(fileName,stuName, scoreDetial):
    fieldnames = ['student', 'problem_name', 'total', 'test_detail']
    with open(fileName, 'a+', newline='') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        if csvfile.tell() == 0:
            writer.writeheader()
        rowData = {}
        rowData['student'] = stuName
        rowData['problem_name'] = scoreDetail['problem_name']
        rowData['total'] = scoreDetail['total']
        rowData['test_detail'] = str(scoreDetail['test_detail'])
        writer.writerow(rowData)

def saveScoreListToCsv(fileName, sortList):
    fieldnames = ['name', 'score']
    with open(fileName, 'a+', newline='') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        if csvfile.tell() == 0:
            writer.writeheader()
        for item in sortList:
            writer.writerow(item)

#===========================================================
# 评测主代码，评测从这里开始
#===========================================================
print('开始验证测试文件....')
if not checkTestData():
    exit(-1)
print('验证测试文件完毕 ok....')

print('移除旧的评测结果....')
if os.path.exists(config.saveFileName):
    os.remove(config.saveFileName)
if os.path.exists(config.sortFileName):
    os.remove(config.sortFileName)
print('移除旧的评测结果 ok....')

print('拷贝代码到评测文件夹....')
judgeDir = copyStuCodeToJudgeDir()
print('拷贝代码到评测文件夹 ok....')

print('正在统计考生信息....')
# 忽略'-'开头的文件夹
students = listStuName(judgeDir)
print('统计完毕,合计: %d ok....'%(len(students)))

print('开始评测考生代码....')

scoreSortList = []
for student in students:
    totalScore = {}
    totalScore['name'] = student
    totalScore['score'] = 0
    for problem in config.problemList:
        scoreDetail = judgeStuCode(judgeDir, student, problem)
        print('考生：%s, 得分[%s]: %d'%(student, problem['english_name'], scoreDetail['total']))
        totalScore['score'] += scoreDetail['total']
        saveScoreDetailToCsv(config.saveFileName, student, scoreDetail)
    scoreSortList.append(totalScore)
print('评测考生代码完毕....')

# 将总成绩排序并保存到文件
scoreSortList.sort(key=lambda k: k.get('score', 0), reverse=True)
saveScoreListToCsv(config.sortFileName, scoreSortList)
print('考生成绩排序完毕....')
